/**
* @module Visitor
*/
define(function(require, exports, module) {
  tree = require('treehugger/tree');
  scriptbuilder = require('scriptbuilder');
  state = require('state');

  function Visitor(rootIden){
    this.rootIden = rootIden;
    scriptbuilder.setRootIden(rootIden);
  };

  Visitor.prototype.reset = function() {
    this.state = new state.State();
    this.state.addRemoteVar(this.rootIden);
  };

  Visitor.prototype.newState = function(){
    return this.state;
  };

  Visitor.prototype.getLocal = function(){
    return this.state.code.local.join("");
  };

  Visitor.prototype.getRemote = function(){
    return this.state.code.remote.join("");
  };

  Visitor.prototype.getInit = function(){
    return this.state.code.init.join("");
  };

  Visitor.prototype.dispatchVisit = function(node, state) {
    // Get a new state object
    state = this.newState();
    node.accept(this, state);
  };

  Visitor.prototype.visit = function(node, args) {
    var state = args[1];
  };

/**
* List visitor
* @ method visitListNode
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitListNode = function(node, args) {
    var state = args[1];
    
    var v = this;
    node.forEach(function(child) {
      child.accept(v, state);
    });
  };

/**
* FUNCTION CALL constructor visitor
* @ method visitConsCall
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsCall = function(node, args) {
    var state = args[1];
    // No remote because remote function calls are not supported..

    //state.calldepth++;
    state.remote = false;

    // Evaluate lhs
    node[0].accept(this, state);

    // Opening brace
    state.code.local.push('(');

    // Evaluate arguments list
    node[1].accept(this, state);

    // Closing brace
    state.code.local.push(')');

    if( node.parent instanceof tree.ListNode ||
        node.parent instanceof tree.ConsNode && node.parent.cons !== "Call" ) {
        state.code.local.push(';\n');
    }

    //state.calldepth--;
  };

/**
* PROPERTY ACCESS constructor visitor
* @method visitConsPropAccess
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsPropAccess = function(node, args) {
    var state = args[1];
    var lhs = node[0];

    var loopOpts = state.loop[state.loopdepth];

    if( loopOpts && loopOpts.initLoop ) {
      loopOpts.initLoop = false;
      lhs.accept(this, state);
      return;
    }

    // Evaluate left hand side
    lhs.accept(this, state);
    
    // Evaluate right hand side
    if( state.remote) {
      state.code.remote.push('.'+node[1].value);
      return;
    }

    state.code.local.push('.'+node[1].value);
  };

/**
* INDEX constructor visitor
* @ method visitConsIndex
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsIndex = function(node, args) {
    var state = args[1];
    throw "Indexes in batches are currently not supported";
  };

/**
* VARIABLE constructor visitor
* @ method visitConsVar
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsVar = function(node, args) {
    var state = args[1];

    if( !state.remote && state.isRemote(node[0].value) ) {
      state.remote = true; 

      state.code.remote.push(scriptbuilder.Remote.OutputVar(node[0].value));
      state.code.local.push(scriptbuilder.Local.OutputVar(node[0].value));
      return;
    }
    
    if( state.remote ) {
      state.code.remote.push(scriptbuilder.Remote.Var(node[0].value));
      return;
    }

    var loopOpts = state.loop[state.loopdepth];
    if( loopOpts && loopOpts.inLoop && loopOpts.mode == "both" ) {
      state.code.local.push(scriptbuilder.Local.Var(node[0].value));
      return;
    }
    
    state.code.local.push(scriptbuilder.Local.Var(node[0].value));
  };

/**
* FOREACH constructor visitor
* @ method visitConsForEach
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsForEach = function(node, args) {
    var state = args[1];

    var callback = node[1][0];
    var name = callback[1][0][0].value;
    var bodyStatements = callback[2];
    
    // Check if left hand side is remote
    node[0].traverseTopDown('Var(x)', function(b) {
      if( state.isRemote(b.x.value) ) {
        state.remote = true;
      }
      else {
        state.remote = false;
      }
    });

    state.loop[state.loopdepth+1] = {
      inLoop: true,
      initLoop: true,
      mode: (state.remote ? 'both' : 'local'),
      item: name
    };

    state.loopdepth++;
    if( state.remote ) {
      // For ...
      state.code.remote.push(scriptbuilder.Remote.LoopStart(name));
      state.code.local.push(scriptbuilder.Local.LoopStart(name));

      // In ...
      node[0].accept(this, state);

      // Do ...
      state.code.remote.push(scriptbuilder.Remote.LoopBlock());

      bodyStatements.accept(this, state);

      // ... End
      state.code.remote.push(scriptbuilder.Remote.End());
      state.code.local.push(scriptbuilder.Local.End());
    }
    else {
      throw "BatchJS: Local foreach currently not supported";
    }
    state.loopdepth--;

  };

/**
* CONDITIONAL constructor visitor
* @ method visitConsIf
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsIf = function(node, args) {
    var state = args[1];    

    var loopOpts = state.loop[state.loopdepth];
    if( loopOpts && loopOpts.inLoop && loopOpts.mode == "both" ) {
      if( !state.remote ) throw "BatchJS: Syntax error, local if statement in remote loop";

      state.code.remote.push(scriptbuilder.Remote.IfStart());
      // Evaluate condition
      node[0].accept(this, state);

      state.code.remote.push(scriptbuilder.Remote.IfBlock());
      // Evaluate statements
      node[1].accept(this, state);

      state.code.remote.push(scriptbuilder.Remote.End());
    }
  }

/**
* STRING constructor visitor
* @ method visitConsString
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsString = function(node, args) {
    var state = args[1];
    
    state.remote ?
      state.code.remote.push('"' + node[0].value + '"') :
      state.code.local.push('"' + node[0].value + '"');
  };

/**
* NUMBER constructor visitor
* @ method visitConsNum
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsNum = function(node, args) {
    var state = args[1];
    
    state.remote ?
      state.code.remote.push(node[0].value) :
      state.code.local.push(node[0].value);
  };

/**
* OPERATION constructor visitor
* @ method visitConsOp
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
  Visitor.prototype.visitConsOp = function(node, args) {
    var state = args[1];

    var op = node[0];
    var lhs = node[1];
    var rhs = node[2];

    var remote = state.remote;
    lhs.accept(this, state);
    state.remote = remote;

    state.remote ?
      state.code.remote.push(' ' + op.value + ' ') :
      state.code.local.push(' ' + op.value + ' ');

    remote = state.remote;
    rhs.accept(this, state);
    state.remote = remote;
  };


/**
* VALUE visitor
* @ method visitValue
*
* @param {Object} node visited node
* @param {Array} args additional arguments
*/
   Visitor.prototype.visitValue = function(node, args) {
    var state = args[1];
    
    state.remote ?
      state.code.remote.push(node.value) :
      state.code.local.push(node.value);
  };

  exports.Visitor = Visitor;
});